/*
 * Copyright 2010 Kevin Bourrillion.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package kevinb9n.tobago;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;

import java.util.List;
import java.util.Random;
import java.util.Set;

public class Island {
  public static Island random(Random r) {
    Board boardA = r.nextBoolean() ? Board.A : Board.a;
    Board boardB = r.nextBoolean() ? Board.B : Board.b;
    Board boardC = r.nextBoolean() ? Board.C : Board.c;

    boolean clockwise = r.nextBoolean();
    List<Board> boards = clockwise
        ? ImmutableList.of(boardA, boardB, boardC)
        : ImmutableList.of(boardA, boardC, boardB);

    boolean isCompassBackwards = r.nextBoolean();
    return new Island(boards, isCompassBackwards, r);
  }

  final List<Board> boards; // 11:00-3:00, 3:00-7:00, 7:00-11:00
  final boolean isCompassBackwards;

  final IslandHex[][] islandHexes = new IslandHex[2 * Board.MAX][];

  final ImmutableSet<IslandHex> hutLocations;
  final ImmutableSet<IslandHex> palmTreeLocations;
  final ImmutableSet<StatuePlacement> statuePlacements;

  public Island(List<Board> boards, boolean compassBackwards, Random r) {
    this.boards = boards;
    isCompassBackwards = compassBackwards;
    
    for (int y = -Board.MAX; y < Board.MAX; y++) {
      islandHexes[y + Board.MAX] = new IslandHex[2 * Board.MAX];
      for (int x = -Board.MAX; x < Board.MAX; x++) {
        try {
          BoardHex boardHex = boardHexAt(x, y);
          islandHexes[y + Board.MAX][x + Board.MAX] = new IslandHex(this, x, y, boardHex);
        } catch (IndexOutOfBoundsException ignore) {
          // not all spaces in the island square are on any board
        }
      }
    }

    // We use a slow and stupid brute-force try-and-retry approach. If we needed
    // to generate many board setups at once, we'd want to speed this up.

    hutLocations = chooseLocations(LandmarkType.HUT, r);
    palmTreeLocations = chooseLocations(LandmarkType.PALM_TREE, r);

    ImmutableSet<IslandHex> statueLocations = chooseLocations(LandmarkType.STATUE, r);

    ImmutableSet.Builder<StatuePlacement> placements = ImmutableSet.builder();
    for (IslandHex hex : statueLocations) {
      hex.setStatueOrientation(StatueOrientation.values()[r.nextInt(6)]);
      placements.add(new StatuePlacement(hex, hex.statueOrientation));
    }
    statuePlacements = placements.build();
  }

  private ImmutableSet<IslandHex> chooseLocations(LandmarkType landmarkType, Random r) {
    Set<IslandHex> locationsChosen = Sets.newLinkedHashSet();
    for (int i = 0; i < landmarkType.count; i++) {
      locationsChosen.add(findHomeFor(landmarkType, locationsChosen, r));
    }
    return ImmutableSet.copyOf(locationsChosen);
  }

  private IslandHex findHomeFor(LandmarkType landmarkType, Set<IslandHex> locationsChosen, Random r) {
    LABEL: while (true) {
      IslandHex candidateHex = islandHexes[r.nextInt(2 * Board.MAX)][r.nextInt(2 * Board.MAX)];
      if (candidateHex == null || !landmarkType.isLegalOn(candidateHex)) {
        continue;
      }
      for (IslandHex other : locationsChosen) {
        if (other.distanceTo(candidateHex) < 4) {
          continue LABEL;
        }
      }
      candidateHex.placeLandmark(landmarkType);
      return candidateHex;
    }
  }

  IslandHex islandHexAt(int x, int y) {
    return islandHexes[y + Board.MAX][x + Board.MAX];
  }

  boolean isOnFirstBoard(int x, int y) {
    return x >= 0 && y >= 0; // by definition
  }

  boolean isOnSecondBoard(int x, int y) {
    return y < 0 && (isCompassBackwards ? (x >= y) : (x > y));
  }

  private BoardHex boardHexAt(int x, int y) {
    if (isOnFirstBoard(x, y)) {
      return boards.get(0).hexAt(x, y);
    }

    if (isOnSecondBoard(x, y)) {
      return boards.get(1).hexAt(-y - 1, x - y - (isCompassBackwards ? 0 : 1));
    }

    return boards.get(2).hexAt(y - x - (isCompassBackwards ? 1 : 0), -x - 1);
  }
}
